# -*- coding: utf-8 -*-  

import requests
import re
import time
import urllib.parse
import random
import sys
from config import Config
from exceptions import *

import datetime
import locale
import platform


__apiJson__ = "https://zh.wikipedia.org/w/api.php?format=json&"
"""Mediawiki API接入点"""

#__configPattern__ =
#r"(?<={{User:gqqnb/ArchiveConfig)(\s*\n*\|\w+\s*=\s*[^\n|]+)+\s*\n*(?=}})"
session = requests.session()

def getTopSections(page,oldId='current'):
    """获取话题列表，不包括导言，不含话题具体内容。
    page： 必须是非标准的、url式的标题，如User_talk:Gqqnb，而不是user talk:gqqnb。

    """
    assert page.find(' ') == -1,"page必须是非标准的、url式的标题，如User_talk:Gqqnb，而不是user talk:gqqnb。"

    r = None
    if(oldId == 'current'):
        r = requests.get(__apiJson__ + "action=parse&prop=sections&page={page}".format(page=page))
    else:
        r = requests.get(__apiJson__ + "action=parse&prop=sections&page={page}&oldid={oldId}".format(page=page,oldId=oldId))
        
    #获取存档标记
    content = r.json()
    sections = content["parse"]["sections"]

    #fromtitle-字符串或布尔值。
    #如果该章节是因调用模板产生，则值为false。
    #如果因嵌入页面产生（如{{API:Parse/zh}}），则值为被嵌页面的名称。
    #如果由源代码里的==XXX==产生，则值为当前页面的名称。
    #详见 https://www.mediawiki.org/wiki/API:Parse/zh#参数
    return list(filter(lambda s:s['toclevel'] == 1 and s['fromtitle'] == page
                       ,sections))



def getSectionText(pageName, sectionIndex,revid='current'):
    """根据页面和章节号获取章节的具体内容。

    pageName：页面名称。如果指定了revid，则此值被忽略。

    """
    assert len(sectionIndex) > 0


    r = None
    if(revid == 'current'):
        r = requests.get(__apiJson__ + "action=query&prop=revisions&titles={title}&rvprop=content&rvsection={sec}".format(title=pageName,sec=sectionIndex))
    else:
        r = requests.get(__apiJson__ + "action=query&prop=revisions&rvprop=content|timestamp&revids={revId}&rvsection={sec}".format(sec=sectionIndex,revId=revid))
    content = r.json()
    content = content['query']['pages']
    content = content[list(content.keys())[0]]
    return content['revisions'][0]['*']

def tellError(e):
    if(e.parameter != ''):
        title = "自动存档的{0}参数设置有误".format(e.parameter)
    else:
        title = "自动存档设置有误"

    sections = getTopSections(e.page)

    for section in sections:
        #同一个错误不重复提醒
        if(section['line'] == title):
            return

    editToken = getEditToken(e.page)

    appendText = "\r\n=={title}==\r\n{message}请参考[[User:Gqqnb/ArchiveConfig]]的最新版本。--~~~~".format(title=title,message=e.message)

    
    print("通知用户" + title),
    content = saveToPage(e.page,editToken,title,appendText=appendText)

    if(('edit' not in content) or (content['edit']['result'] != 'Success')):
        print("失败！" + content)
    return

def getEditToken(archivePage):
    r = session.post(__apiJson__ + "action=query&prop=info&intoken=edit&titles={title}".format(title=archivePage))
    content = r.json()
    content = content['query']['pages']
    content = content[list(content.keys())[0]]
    editToken = content['edittoken']
    
    if(editToken == '+\\'):
        raise Exception("获取编辑令牌失败！")
    return editToken

def getConfig(leadSection):
    '''搜索导言中的存档设置。'''

    match = re.search(Config.pattern,leadSection,re.DOTALL)
    if match != None:
        configText = match.group(0)

        return Config(configText)
    else:
        return None

def increseConfigCounter(leadSection,increasedConfig):
    match1 = re.search(Config.pattern,leadSection,re.DOTALL)

    first = leadSection[:match1.start()]
    third = leadSection[match1.end():]

    return first + "\r\n" + str(increasedConfig) + third

def getOldRevisionId(pageName, oldDays):
    '''获取指定页面几天前的修订版本号。
    如果oldDays前该页面尚未建立，则返回None。   
    
    '''

    oldVersionTime = time.strftime('%Y-%m-%dT%H:%M:%SZ',time.localtime(time.time() - oldDays * 24 * 3600))
    
    r = requests.get(__apiJson__ + "action=query&prop=revisions&titles={title}&rvprop=ids&rvstart={rvDate}&rvlimit=1".
                     format(title=pageName,rvDate=oldVersionTime))
    content = r.json()
    content = content['query']['pages']
    content = content[list(content.keys())[0]]

    if('revisions' not in content):
        return None

    revid = content['revisions'][0]['revid']
    return revid

def separateToSection(fullText, sections):
    sectionTextList = []
    lastOffset = -1
    for i  in range(len(sections) - 1,-1,-1):
        offset = int(sections[i]['byteoffset'])
        text = fullText[offset:lastOffset]
        assert text.startswith('==')
        sectionTextList.insert(0,text)
        lastOffset = offset - 1
    return sectionTextList

def findOldTopics(pageName,currentSections,oldRevisionId, oldSections,maxSave):
    """查找指定页面的哪些话题可以存档。
    返回当前页面去除可存档话题后的文本、可存档话题组成的文本、可存档的话题总数。
    
    """

    r = requests.get(__apiJson__ + "action=query&prop=revisions&titles={title}&rvprop=content".format(title=pageName))
    content = r.json()
    content = content['query']['pages']
    content = content[list(content.keys())[0]]
    currentPageText = content['revisions'][0]['*']

    r = requests.get(__apiJson__ + "action=query&prop=revisions&rvprop=content&revids={revId}".format(title=pageName,revId=oldRevisionId))
    content = r.json()
    content = content['query']['pages']
    content = content[list(content.keys())[0]]
    oldPageText = content['revisions'][0]['*']

    currentSectionTextList = separateToSection(currentPageText, currentSections)
    oldSectionTextList = separateToSection(oldPageText, oldSections)

            
    activePageEditText = '' #用于提交到活跃页的文本
    archivePageAppendText = '' #用于追加到存档页的文本（新章节）
    archiveThreadsCount = 0
    for sectionText in currentSectionTextList:
        toSave = False
        for i in range(len(oldSectionTextList)):
            oldSectionText = oldSectionTextList[i]
            if(maxSave > 0 and sectionText == oldSectionText):
                print("可以存档：" + sectionText[2:sectionText.index('==',2)])

                archivePageAppendText+="\r\n" + "\r\n" + sectionText
                maxSave-=1
                archiveThreadsCount+=1

                oldSections.pop(i)
                toSave = True
                break

        #旧版本里都找遍了都没找到匹配的话题
        if(toSave == False):
            activePageEditText+="\r\n" + "\r\n" + sectionText


    return activePageEditText,archivePageAppendText,archiveThreadsCount

def saveToPage(pageName,editToken, summary,appendText='',  fullText='', baseTimestamp=''):
    '''将文本保存或追加到页面。返回服务器返回的内容。'''

    data = dict(summary=summary,title=pageName,
                bot="true", action="edit", token=editToken, format="json")

    if(appendText != ''):
        data['appendtext'] = appendText
    elif(fullText != ''):
        data['text'] = fullText
    else:
        raise Exception()

    if(baseTimestamp != ''):
        data['basetimestamp'] = baseTimestamp

    r = session.post("https://zh.wikipedia.org/w/api.php",
                        data=data,files=dict(hello="hello",world="world"))
        
    return r.json()


def formatArchivePageName(activePageName, config):
    '''根据设置构造存档页名称。返回的是绝对路径。'''

    lt = time.localtime(time.time())
    d = datetime. datetime.fromtimestamp(time.mktime(lt))

    if(config.archiveByCounter):
        archivePageName = config['archive'].format(counter=config['counter'],datetime=d)
    
        if(archivePageName[0] == '/'):
            archivePageName = activePageName + archivePageName
    else:
        raise FormatException(activePageName,"目前存档页仅支持计数命名法。",'archive')
    return archivePageName

def isCurrentArchiveFull(activePageName, archivePageName, config, content):
    if('maxarchivesize' in config):
        if(isinstance(config['maxarchivesize'],int) == False and config['maxarchivesize'].isdigit() == False):
            raise FormatException(activePageName,"maxarchivesize的值" + config['maxarchivesize'] + "不是有效的数字。",'maxarchivesize')
    
        maxArchiveSize = int(config['maxarchivesize']) * 1024
    
        if(config.archiveByCounter and maxArchiveSize > 0 and content['length'] > maxArchiveSize):
            return True
    
    if('maxarchivethreads' in config):
        if(isinstance(config['maxarchivethreads'],int) == False and config['maxarchivethreads'].isdigit() == False):
            raise FormatException(activePageName,"maxarchivethreads的值" + config['maxarchivethreads'] + "不是有效的数字。",'maxarchivethreads')
    
        maxArchiveThreads = int(config['maxarchivethreads'])
    
        archiveSections = getTopSections(archivePageName)
    
        if(config.archiveByCounter and maxArchiveThreads > 0 and len(archiveSections) > maxArchiveThreads):
            return True
    
    return False

def archiveCore(activePageName,retry=3):
    """尝试对指定的页存档。
    activePageName: 要对这个页面的话题存档。必须是url形式的标题，如User_talk:Gqqnb，
                    而不是user talk:gqqnb。

    """
    for currentTry in range(retry):
        time.sleep(currentTry * 5)

        r = requests.get(__apiJson__ + "action=query&prop=revisions&rvsection=0&titles={page}&rvprop=content|timestamp".format(page=activePageName))
        content = r.json()    
        content = content['query']['pages']
        content = content[list(content.keys())[0]]['revisions'][0]
    
        activeTimestamp = content['timestamp']
        section0 = content['*']
    
        config = getConfig(section0)    
        if(config == None):
            raise FormatException(activePageName,"未在导言中找到存档设置。")

    
        topicOld = int(config['old']) #多少天未更改的话题可以存档
        
        sections = getTopSections(activePageName)
    
        maxSave = len(sections) - int(config['minthreadsleft']) #最多能存档多少个话题
        if(maxSave <= 0):
            return

        #可能可以存档一些话题
        oldRevisionId = getOldRevisionId(activePageName, topicOld)    
        if(oldRevisionId == None):
            return

        oldSections = getTopSections(activePageName,oldRevisionId)
        activePageEditText,archivePageAppendText,archiveThreadsCount = findOldTopics(activePageName,sections,oldRevisionId, oldSections,maxSave)

                
        if(archiveThreadsCount < config['minthreadstoarchive']):
            return

        #检查目标页是否存在
        archivePageName = formatArchivePageName(activePageName, config)

        r = requests.get(__apiJson__ + "action=query&titles={title}&prop=info&inprop=".format(title=archivePageName))
        content = r.json()
        content = content['query']['pages']
        content = content[list(content.keys())[0]]

        if('missing' in content): #创建新的存档页，加入存档头
            archivePageAppendText = config['archiveheader'] + "\r\n\r\n" + archivePageAppendText
        else:  #检查当前存档页的大小和话题数
            if(isCurrentArchiveFull(activePageName, archivePageName, config, content)):
                #创建新的存档页，加入存档头
                archivePageAppendText = config['archiveheader'] + "\r\n\r\n" + archivePageAppendText

                assert(config.archiveByCounter)

                config['counter'] = int(config['counter']) + 1
                archivePageName = formatArchivePageName(activePageName, config)

                print(config)

                section0 = increseConfigCounter(section0,config)


        editToken = getEditToken(activePageName)

        #从讨论页中删除已存档的话题
        activePageEditText = section0 + "\r\n\r\n" + activePageEditText
        summary = "机器人：将{0}个早于{1}天的话题存档至[[{2}]]".format(archiveThreadsCount,topicOld,archivePageName)

        content = saveToPage(activePageName,editToken,summary,fullText=activePageEditText,baseTimestamp=activeTimestamp) 
        
        if('error' in content):
            if(content['error']['code'] == 'editconflict'):
                print("发生编辑冲突，重试")
                continue
            else:
                raise Exception("从{0}移除旧话题失败：{1}".format(activePageName,content['error']['code']))
        else:
            if(content['edit']['result'] == 'Success'):
                pass
            else:
                raise Exception("从{0}移除旧话题失败：{1}".format(activePageName,content['edit']))


        summary = "机器人：存档{0}个来自[[{1}]]的话题".format(archiveThreadsCount,activePageName)
        content = saveToPage(archivePageName, editToken, summary,appendText=archivePageAppendText) 
        if('error' in content):
            raise Exception("{0}加入旧话题失败：{1}".format(archivePageName,content['error']['code']))
        else:
            if(content['edit']['result'] == 'Success'):
                pass
            else:
                raise Exception("{0}加入旧话题失败：{1}".format(archivePageName,content['edit']))
        print(activePageName + ": " + summary)
        return
    return

def archive(activePageName):
    try:
        archiveCore(activePageName)
    except FormatException as e:
        tellError(e)

def login(username,password):
    
    username = urllib.parse.quote(username)
    password = urllib.parse.quote(password)
    
    r = session.post(__apiJson__ + "action=login&lgname={username}&lgpassword={password}".
                      format(username=username,password=password))
    
    content = r.json()
    content = content['login']
    if(content['result'] == 'NeedToken'):
        cookies = r.cookies
        token = content['token']
        r = session.post(__apiJson__ + "action=login&lgname={username}&lgpassword={password}&lgtoken={token}".
                      format(username=username,password=password,token=token))
        content = r.json()
        content = content['login']
        assert content['result'] == "Success"

    print("登录成功")
    return


if(len(sys.argv) > 2):
    username = sys.argv[1]
    password = sys.argv[2]


if(platform.system()=='Windows'):
    #http://stackoverflow.com/questions/20762326/python3-3-format-with-unicode-format-spec
    locale.setlocale(locale.LC_CTYPE, 'chinese')


lt = time.localtime(time.time())
d = datetime. datetime.fromtimestamp(time.mktime(lt))
print('{datetime:%B %d, %Y}'.format(datetime=d))
print('{datetime:%Y}年'.format(datetime=d))
print('{datetime:%Y年%m月-%dT%H:%M:%SZ}'.format(datetime=d))
print('{datetime:%Y-どん屋-%m月-%dT%H:%M:%SZ}'.format(datetime=d))

print("格式化测试完毕\r\n");

login(username,password)
#archive('User_talk:Gqqnb')
#archive('Talk:2013年芦山地震')
#archive('Talk:朱令铊中毒事件')
#archive('Talk:法轮功')
#archive('2011年甬台温铁路列车追尾事故')
#archive('User:Gqqnb/沙盒')

#内测第一批
#archive('User_talk:Liuxinyu970226')
#archive('User_talk:Byfserag')
#archive('User_talk:9shi')
archive('User_talk:Tonybuaa')


#editToken = getEditToken("User:Gqqnb/沙盒")

#r = requests.get(__apiJson__ + "action=query&prop=revisions&rvsection=0&titles={page}&rvprop=content|timestamp".format(page="User:Gqqnb/沙盒"))
#content = r.json()    
#content = content['query']['pages']
#content = content[list(content.keys())[0]]['revisions'][0]
    
#activeTimestamp ='2013-12-22T06:12:19Z' #content['timestamp']

#r = session.post("https://zh.wikipedia.org/w/api.php",
#        data=dict(text="test",
#                  basetimestamp=activeTimestamp,
#                    summary="存档测试",
#                    title="User:Gqqnb/沙盒",
#                    bot="true", action="edit", token=editToken, format="json"),
#        files=dict(hello="hello",world="world"))
            

#content = r.json()


#content = content['edit']
#if(content['result'] != 'Success'):
#    raise Exception()






